package com.noticeditorteam.noticeditor.controller; import com.noticeditorteam.noticeditor.Main; import com.noticeditorteam.noticeditor.io.IOUtil; import com.noticeditorteam.noticeditor.model.*; import com.noticeditorteam.noticeditor.view.Chooser; import com.noticeditorteam.noticeditor.view.Notification; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.Locale; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.event.ActionEvent; import javafx.event.Event; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Scene; import javafx.scene.control.ListView; import javafx.scene.control.MenuItem; import javafx.scene.control.RadioMenuItem; import static javafx.scene.control.SelectionMode.SINGLE; import javafx.scene.control.TextArea; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.stage.Modality; import javafx.stage.Stage; import static org.pegdown.Extensions.*; import org.pegdown.PegDownProcessor; /** * @author Edward Minasyan <mrEDitor@mail.ru> */ public class NoticeViewController implements Initializable { // @att:filename.png private static final Pattern ATTACHMENT_PATTERN = Pattern.compile("@att\\:([a-zA-Z0-9._\\(\\)]+)"); @FXML private MenuItem importAttachFromUrlItem, exportAttachItem, deleteAttachItem; @FXML private ListView<Attachment> attachsView; private ResourceBundle resources; @FXML private TextArea editor; @FXML private WebView viewer; private final ObjectProperty<Attachment> currentAttachmentProperty = new SimpleObjectProperty<>(null); protected final PegDownProcessor processor; protected final SyntaxHighlighter highlighter; private WebEngine engine; private Main main; private String codeCssName; public NoticeViewController() { processor = new PegDownProcessor(AUTOLINKS | TABLES | FENCED_CODE_BLOCKS); highlighter = new SyntaxHighlighter(); } @Override public void initialize(URL url, ResourceBundle rb) { highlighter.unpackHighlightJs(); engine = viewer.getEngine(); editor.textProperty().addListener((o, oldValue, newValue) -> changeContent(newValue)); attachsView.getSelectionModel().setSelectionMode(SINGLE); attachsView.getSelectionModel().selectedItemProperty().addListener((o, oldValue, newValue) -> { currentAttachmentProperty.setValue((Attachment) newValue); }); exportAttachItem.disableProperty().bind(Bindings.isNull(currentAttachmentProperty)); deleteAttachItem.disableProperty().bind(Bindings.isNull(currentAttachmentProperty)); resources = rb; } public TextArea getEditor() { return editor; } private void changeContent(String newContent) { final NoticeTreeItem current = getCurrentNotice(); String parsed = processor.markdownToHtml(newContent); if (current != null) { current.changeContent(newContent); parsed = parseAttachments(parsed, current.getAttachments()); } engine.loadContent(highlighter.highlight(parsed, codeCssName)); } private String parseAttachments(String text, Attachments attachments) { if (attachments.isEmpty()) return text; final Matcher m = ATTACHMENT_PATTERN.matcher(text); final StringBuffer sb = new StringBuffer(); while (m.find()) { final String name = m.group(1); Attachment att = attachments.get(name); if (att != null) { m.appendReplacement(sb, "<img src=\"data:image/png;base64, " + att.getDataAsBase64() + "\"/>"); } } m.appendTail(sb); return sb.toString(); } public final EventHandler<ActionEvent> onPreviewStyleChange = new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { final int ordinal = (int) ((RadioMenuItem) e.getSource()).getUserData(); PreviewStyles style = PreviewStyles.values()[ordinal]; // CSS path String path = style.getCssPath(); if (path != null) { path = getClass().getResource(path).toExternalForm(); } codeCssName = style.getCodeCssName(); engine.setUserStyleSheetLocation(path); changeContent(editor.textProperty().getValue()); } }; public final EventHandler<ActionEvent> onThemeChange = new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent e) { // Remove all previous themes if exists for (Themes value : Themes.values()) { if (value.getCssPath() != null) { String path = getClass().getResource(value.getCssPath()).toExternalForm(); main.getPrimaryStage().getScene().getStylesheets().remove(path); } } // Add new theme final int ordinal = (int) ((RadioMenuItem) e.getSource()).getUserData(); Themes theme = Themes.values()[ordinal]; String path = theme.getCssPath(); if (path != null) { path = getClass().getResource(path).toExternalForm(); main.getPrimaryStage().getScene().getStylesheets().add(path); } } }; public void rebuildAttachsView() { onAttachsFocused(null); } private void exportAttachment(File file, Attachment attachment) { try { IOUtil.writeContent(file, attachment.getData()); Notification.success(resources.getString("export.success")); } catch (IOException e) { NoticeController.getLogger().log(Level.SEVERE, null, e); Notification.error(resources.getString("export.error")); } } @FXML private void handleContextMenu(ActionEvent event) { final Object source = event.getSource(); if (source == importAttachFromUrlItem) { if (getCurrentNotice() == null) return; try { final ResourceBundle resource = ResourceBundle.getBundle( "resources.i18n.AttachmentImport", Locale.getDefault()); final Stage stage = new Stage(); stage.setTitle(resource.getString("title")); stage.initOwner(main.getPrimaryStage()); stage.initModality(Modality.WINDOW_MODAL); FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/AttachmentImport.fxml"), resource); Scene scene = new Scene(loader.load()); stage.setScene(scene); stage.show(); } catch (Exception ex) { NoticeController.getLogger().log(Level.SEVERE, "importAttachmentContextMenu", ex); } return; } if (source == exportAttachItem) { if (currentAttachmentProperty.get() == null) return; File fileSaved = Chooser.file().save() .filter(Chooser.ALL) .title(resources.getString("exportfile")) .show(main.getPrimaryStage()); if (fileSaved == null) return; exportAttachment(fileSaved, currentAttachmentProperty.get()); return; } if (source == deleteAttachItem) { if (currentAttachmentProperty.get() == null) return; final NoticeTreeItem current = getCurrentNotice(); current.getAttachments().remove(currentAttachmentProperty.get()); rebuildAttachsView(); } } @FXML private void onAttachsFocused(Event event) { final NoticeTreeItem current = getCurrentNotice(); attachsView.getItems().clear(); if (current != null && current.isLeaf()) { for (Attachment attachment : current.getAttachments()) { attachsView.getItems().add(attachment); } } } public void setMain(Main main) { this.main = main; } private NoticeTreeItem getCurrentNotice() { return NoticeController.getNoticeTreeViewController().getCurrentNotice(); } }